#ifndef SCAXIS_H
#define SCAXIS_H

#include "../MotionManager/motionelementcontainer.h"
#include "../motionutility.h"
#include "MoveProtection/moveprotection.h"
#include "axisconfig.h"
#include "errorHandling/silicolerror.h"
#include "globalstate.h"
#include "loghelper.h"
#include "scdi.h"
#include "scdo.h"
#include "scmotion_global.h"
#include "vcmconfig.h"
#include <QThread>
#include <math.h>

class SCMOTIONSHARED_EXPORT SCAxis : public QObject
{
    Q_OBJECT
public:
    enum Direction
    {
        Positive,
        Negative
    };
    Q_ENUM(Direction)

    static Direction oppositeDir(Direction dir);

    SCAxis(QString name, QObject *parent = nullptr);

    static MotionElement::Type elementType()
    {
        return MotionElement::Axis;
    }

    static EnumHelper<Direction> &DirEnumInfo()
    {
        static EnumHelper<Direction> instance(SCAxis::staticMetaObject, "Direction");
        return instance;
    }

    QString name() const
    {
        return objectName();
    }

    AxisConfig *config() const
    {
        return m_config;
    }

    virtual void setConfig(AxisConfig *config)
    {
        m_config = config;
    }

    void setMoveProtection(MoveProtection *moveProtection)
    {
        this->moveProtection = moveProtection;
    }

    bool isInit() const
    {
        return m_isInit;
    }

    bool isEnable() const
    {
        return m_isEnable;
    }

    bool hasHome() const
    {
        return m_hasHome;
    }

    double velocityRatio() const
    {
        return m_config->velocityRatio();
    }

    ///
    /// \brief needReHome
    /// Set hasHome to false
    ///
    void needReHome();

    void init();

    Q_INVOKABLE void home(bool waitDone = true, int timeout = 30000);
    void waitHomeDone(int timeout = 30000);

    ///
    /// \brief getFeedbackPos
    /// Get feedback pos from cache.
    /// Software will push the feedback pos to cache, and give it
    /// a timestamp. If the elapsed time is greater than the given
    /// 'cacheInvalidationTime', software will update cache first.
    /// If you want to get the realtime feedback pos, give 'cacheInvalidationTime'
    /// with -1.
    /// \param cacheInvalidationTime ms
    /// \return
    ///
    double getFeedbackPos(int cacheInvalidationTime = 2);
    virtual double getCurrentOutputPos() = 0;
    double getCurrentTargetPos() const
    {
        return currentTargetPos;
    }
    virtual void setFeedbackPos(double pos) = 0;
    virtual void setCurrentPos(double pos) = 0;

    Q_INVOKABLE void enable();
    Q_INVOKABLE void disable();

    virtual bool hasAlarm() = 0;
    virtual bool isReady() const = 0;
    virtual bool isInPos() const = 0;
    virtual bool isRunning() const = 0;

    Q_INVOKABLE void clearAlarm();

    void stop();
    Q_INVOKABLE void stopAndResetState();
    //    void emergencyStop();

    virtual double positiveLimit() const = 0;
    virtual double negativeLimit() const = 0;
    virtual double maxVelocity() const = 0;

    Q_INVOKABLE void absMoveWithLimit(double targetPos);
    Q_INVOKABLE void relMoveWithLimit(double step);

    bool isAtPos(double pos, double precision)
    {
        return !isRunning() && qAbs(getFeedbackPos(-1) - pos) < precision;
    }
    Q_INVOKABLE void absMove(double targetPos, bool waitDone = true, double precision = 0.1, int timeout = 30000);
    Q_INVOKABLE void relMove(double step, bool waitDone = true, double precision = 0.1, int timeout = 30000);

    void tryToMove(
        double targetPos, uint checkInterval = 50, int stepCount = 10, double precision = 0.1, int timeout = 60000);

    void waitArrivedPos(double targetPos, double precision = 0.1, int timeout = 30000);
    void waitArrivedPos(double precision = 0.1, int timeout = 30000);
    void waitGreaterThanPos(double targetPos, double precision = 0.1, int timeout = 30000);
    void waitLessThanPos(double targetPos, double precision = 0.1, int timeout = 30000);

    ///
    /// \brief absForceMove
    /// If axis is running, force move will change the target pos online.
    /// Else will call the absMove or relMove.
    /// Force move will never wait move done!
    /// \param targetPos
    ///
    Q_INVOKABLE void absForceMove(double targetPos);
    Q_INVOKABLE void relForceMove(double step);
    Q_INVOKABLE void relForceMoveWithLimit(double step);

    ///
    /// \brief scaleMaxVel
    /// set velocity ratio, will not change the ratio in config
    /// \param ratio
    ///
    void scaleMaxVel(double ratio);

    void scaleMaxAcc(double ratio);
    void resetMaxAcc();

    ///
    /// \brief resetMaxVel
    /// set velocity ratio with the ratio in config
    ///
    void resetMaxVel();

    virtual double getCurrentVel() = 0;

    ///
    /// \brief jogMove
    /// Internal function. Just for corresponding UI.
    /// \param direction    0: Negative  1: Positive
    /// \param velocityLimit    velocity ratio limit
    ///
    Q_INVOKABLE void jogMove(int direction, double velocityLimit);
    ///
    /// \brief jogMoveWithVel
    /// Internal function. Just for corresponding UI.
    /// \param direction    0: Negative  1: Positive
    /// \param velocity     mm/s
    ///
    Q_INVOKABLE void jogMoveWithVel(int direction, double velocity);

    void moveToPositiveLimit(bool waitDone = true, double velocityLimit = 1);
    void moveToNegativeLimit(bool waitDone = true, double velocityLimit = 1);
    void slowMoveTo(double targetPos, bool waitDone = true, double velocityLimit = 1);

    void velocityMove(Direction dir);

    Q_INVOKABLE void startReciprocate(double pos1, int delay1, double pos2, int delay2, int times);
    Q_INVOKABLE void stopReciprocate();

    void setMasterAxis(SCAxis *masterAxis);
    SCAxis *masterAxis() const;
    Q_INVOKABLE bool hasMasterAxis();
    Q_INVOKABLE bool hasBindedToMaterAxis();
    Q_INVOKABLE bool bindToMasterAxis();
    Q_INVOKABLE bool unBindToMasterAxis();

    Q_INVOKABLE virtual void clearStatus() {}

    //    void setTrigOutput();
    //    void clearTrigOutput();

protected:
    void checkState(bool checkHasHome = true);
    void checkIsReady();
    void checkInLimitRange(double targetPos) const;

    ///
    /// \brief initImpl
    /// You can throw SilicoolAbort if any error occur.
    ///
    virtual void initImpl() = 0;

    virtual void homeImpl() = 0;
    virtual bool isHomeDone() = 0;
    virtual QString homeErrorMsg() = 0;
    ///
    /// \brief operationAfterHome
    /// You may clear current pos and feedback pos after home.
    ///
    virtual void operationAfterHome() = 0;
    virtual void stopImpl() = 0;
    virtual void stopHome() = 0;

    virtual void enableImpl() = 0;
    virtual void disableImpl() = 0;

    virtual void clearErrorImpl() = 0;

    //    virtual void emergencyStopImpl() = 0;

    virtual void moveToImpl(double targetPos) = 0;

    virtual void velocityMoveImpl(Direction dir) = 0;

    virtual double getFeedbackPosImpl() = 0;

    virtual void changeTargetPosOnlineImpl(double targetPos) = 0;

    virtual bool bindToMasterAxisImpl(bool bind) = 0;

    virtual void scaleMaxAccImpl(double ratio) = 0;

protected slots:
    virtual void scaleMaxVelImpl(double ratio) = 0;

private:
    void startHome();

    void absMoveImpl(double targetPos);

    void getPosInLimitRange(double &targetPos);

    void waitControllerHomeDone(int timeout = 30000);

    void waitDriverHomeDone(int timeout = 30000);

    // -------------- For Vcm --------------

public:
    virtual void fastSoftLanding(double vel, double targetPos)
    {
        Q_UNUSED(vel)
        Q_UNUSED(targetPos)
        throw SilicolAbort("Unimplemented function: fastSoftLanding");
    }
    void softLandPosDown(QString softLandingPos, bool waitDone = true);
    void
    softLandDown(double vel, double targetPos, double force, double margin, bool waitDone = true, int timeout = 30000);
    void waitSoftLandDownFinished(double vel, double targetPos, double force, double margin, int timeout = 30000);
    void waitSoftLandUpFinished(int timeout = 30000);
    void softLandUp(bool waitDone = true, int timeout = 30000);
    bool isSoftlandingDown() const
    {
        return m_isSoftLandDown;
    }
    Q_INVOKABLE double measureHeight(double vel, double force, int holdTime);

protected:
    virtual void softLandDownImpl(double vel, double targetPos, double force, double margin)
    {
        Q_UNUSED(vel)
        Q_UNUSED(targetPos)
        Q_UNUSED(force)
        Q_UNUSED(margin)
        throw SilicolAbort("Unimplemented function: softLandDownImpl");
    }

    virtual void softLandUpImpl()
    {
        throw SilicolAbort("Unimplemented function: softLandUpImpl");
    }

    virtual bool isSoftLandDownFinished()
    {
        throw SilicolAbort("Unimplemented function: isSoftLandDownFinished");
    }

    virtual bool isSoftLandUpFinished()
    {
        throw SilicolAbort("Unimplemented function: isSoftLandUpFinished");
    }

private:
    void startSoftLandDown(double vel, double targetPos, double force, double margin);
    void startSoftLandUp();

private:
    const double MinStep = 0.0001;

    MoveProtection *moveProtection = nullptr;
    bool isConnectVelRatioChangedSignal = false;
    bool isReciprocating = false;
    AxisConfig *m_config = nullptr;
    bool m_isInit = false;
    bool m_isEnable = false;
    bool m_hasHome = false;
    double currentTargetPos = 0;
    double lastFeedbackPos = 0;
    qint64 lastUpdateFeedbackPosTimeStamp = 0;
    double currentAccRatio = -1;
    bool hasStop = false;
    bool m_hasBindToMasterAxis = false;
    SCAxis *m_masterAxis = nullptr;

    AxisConfig::HomeExecutor homeExecutor;
    SCDI *homeDoneDi = nullptr;
    SCDO *startHomeDo = nullptr;

    bool m_isSoftLandDown = false;
};

#endif    // SCAXIS_H
